In functional programming, functions can be treated as objects. That is, they can assigned to a variable, can be passed as arguments or even returned from other functions.
In [2]:
a = 10
def test_function():
pass
print(id(a), dir(a))
print(id(test_function), dir(test_function))
The simplest way to initialize a pure function in python is by using lambda
keyword, which helps in defining the one-line function. Functions initialized with lambda can often called anonymous functions
In [2]:
# Example lambda keyword
product_func = lambda x, y: x*y
print(product_func(10, 20))
print(product_func(10, 2))
In [3]:
concat = lambda x, y: [x, y]
print(concat([1,2,3], 4))
Functions are first-class objects in Python, meaning they have attributes and can be referenced and assigned to variables.
In [4]:
def square(x):
"""
This returns the square of the requested number `x`
"""
return x**2
print(square(10))
In [5]:
print(square(100))
In [6]:
# Assignation to another variable
mySquare = square
print(mySquare(100))
print(square)
print(mySquare)
print(id(square))
print(id(mySquare))
In [7]:
# attributes present
print("*"*30)
print(dir(square))
print("*"*30)
print(mySquare.__name__)
print("*"*30)
print(square.__code__)
print("*"*30)
print(square.__doc__)
In [8]:
square.d = 10
print(dir(square))
Python also supports higher-order functions, meaning that functions can accept other functions as arguments and return functions to the caller.
In [3]:
print(square(square(square(2))))
In [12]:
product_func = lambda x, y: x*y
sum_func = lambda F, m: lambda x, y: F(x, y)+m
print(sum_func(product_func, 5)(2, 4))
In [13]:
print(sum_func)
In [16]:
print(sum_func(product_func, 5))
In [15]:
print(sum_func(product_func, 5)(3, 5))
13=2*4+5
F -> product_func
m => 5
x -> 2
y -> 4
2*4+5 = 8+5 = 13
In the above example higher-order function that takes two inputs- A function F(x)
and a multiplier m
.
In Python, Function(s) can also be defined within the scope of another function. If this type of function definition is used the inner function is only in scope inside the outer function, so it is most often useful when the inner function is being returned (moving it to the outer scope) or when it is being passed into another function.
Notice that in the below example, a new instance of the function inner()
is created on each call to outer()
. That is because it is defined during the execution of outer()
. The creation of the second instance has no impact on the first.
In [9]:
def outer(a):
"""
Outer function
"""
y = 0
def inner(x):
"""
inner function
"""
y = x*x*a
return(y)
print(a)
return inner
my_out = outer
In [10]:
my_out(102)
Out[10]:
In [12]:
o = outer(10)
b = outer(20)
print("*"*20)
print(b)
print(o)
print("*"*20)
print(o(10))
print(b(10))
In [13]:
def outer():
"""
Outer function
"""
if 'a' in locals():
a +=10
else:
print("~"),
a = 20
def inner(x):
"""
inner function
"""
return(x*x*a)
print(a)
return inner
# oo = outer
# print(oo.__doc__)
o = outer()
print("*"*20)
print(o)
print(o(10))
print(o.__doc__)
In [14]:
b = outer()
print(b)
print(b(30))
print(b.__doc__)
In [15]:
x = 0
def outer():
x = 1
def inner():
x = 2
print("inner:", x)
inner()
print("outer:", x)
outer()
print("global:", x)
lets take the above example, we have two functions, outer
& inner
. We also have x
variable which is present as global
and also present in both the functions.
If we want to access x
of outer
function from inner
function than global
keyword not help. Fortunately, Python provides a keyword nonlocal
which allows inner
functions to access variables to outer
functions as shown in below example.
The details of nonlocal are details in https://www.python.org/dev/peps/pep-3104/
In [14]:
x = 0
def outer():
x = 1
def inner():
nonlocal x
x = 2
print("inner:",x, "id:", id(x))
inner()
print("outer:",x, "id:", id(x))
outer()
print("global:",x, "id:", id(x))
In [34]:
def outer(a):
"""
Outer function
"""
y = 1
def inner(x):
"""
inner function
"""
nonlocal y
print(y)
y = x*x*a
return("y =" + str(y))
print(a)
return inner
In [38]:
o = outer(10)
b = outer(20)
print("*"*20)
print(o)
print(o(10))
print("*"*20)
print(b)
print(b(10))
You use inner functions to protect them from anything happening outside of the function, meaning that they are hidden from the global scope.
In [3]:
# Encapsulation
def increment(current):
def inner_increment(x): # hidden from outer code
return x + 1
next_number = inner_increment(current)
return [current, next_number]
print(increment(10))
NOTE: We can not access directly the inner function as shown below
In [4]:
try:
increment.inner_increment(109)
except Exception as e:
print(e)
In [20]:
# Keepin’ it DRY
def process(file_name):
def do_stuff(file_process):
for line in file_process:
print(line)
if isinstance(file_name, str):
with open(file_name, 'r') as f:
do_stuff(f)
else:
do_stuff(file_name)
process(["test", "test3", "t33"])
# process("test.txt")
or have similar logic which can be replaced by a function, such as mathematical functions, or code base which can be clubed by using some parameters.
In [21]:
def square(n):
return n**2
def cube(n):
return n**3
print(square(2))
In [22]:
def sqr(a, b):
return a**b
??? why code
In [32]:
def test():
print("TEST TEST TEST")
def yes(name):
print("Ja, ", name)
return True
return yes
d = test()
print("*" * 14)
a = d("Murthy")
print("*" * 14)
print(a)
In [38]:
def power(exp):
def subfunc(a):
return a**exp
return subfunc
square = power(2)
hexa = power(6)
print(square)
print(hexa)
print(square(5)) # 5**2
print()
print(hexa(3)) # 3**6
print(power(6)(3))
# subfunc(3) where exp = 6
# SQuare
# exp -> 2
# Square(5)
# a -> 5
# 5**2
# 25
In [ ]:
Power(6)(3, x)
In [48]:
def a1(m):
x = m * 2
def b(v, t=None):
if t:
print(x, m, t)
return v + t
else:
print(x, m, v)
return v + x
return b
n = a1(2)
print(n(3))
print(n(3, 10))
In [52]:
def f1(a):
def f2(b):
return f2
def f3(c):
return f3
def f4(d):
return f4
def f5(e):
return f5
print (f1(1)(2)(3)(4)(5))
In [55]:
def f1(a):
def f2(b):
def f3(c):
def f4(d):
def f5(e):
print(e)
return f5
return f4
return f3
return f2
f1(1)(2)(3)(4)(5)
They are techniques for implementing lexically scoped name binding with first-class functions. It is a record, storing a function together with an environment. a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created.
A closure—unlike a plain function—allows the function to access those captured variables through the closure's copies of their values or references, even when the function is invoked outside their scope.
In [12]:
def f(x):
def g(y):
return x + y
return g
def h(x):
return lambda y: x + y
a = f(1)
b = h(1)
print(a, b)
print(a(5), b(5))
print(f(1)(5), h(1)(5))
both a and b are closures—or rather, variables with a closure as value—in both cases produced by returning a nested function with a free variable from an enclosing function, so that the free variable binds to the parameter x of the enclosing function. However, in the first case the nested function has a name, g, while in the second case the nested function is anonymous. The closures need not be assigned to a variable, and can be used directly, as in the last lines—the original name (if any) used in defining them is irrelevant. This usage may be deemed an "anonymous closure".
1: Copied from : "https://en.wikipedia.org/wiki/Closure_(computer_programming)"
In [25]:
def make_adder(x):
def add(y):
return x + y
return add
plus10 = make_adder(10)
print(plus10(12)) # make_adder(10).add(12)
print(make_adder(10)(12))
Closures can avoid the use of global values and provides some form of data hiding. It can also provide an object oriented solution to the problem.
When there are few methods (one method in most cases) to be implemented in a class, closures can provide an alternate and more elegant solutions. But when the number of attributes and methods get larger, better implement a class.
In functional programming, functions can be treated as objects. That is, they can assigned to a variable, can be passed as arguments or even returned from other functions.
In [2]:
a = 10
def test_function():
pass
print(id(a), dir(a))
print(id(test_function), dir(test_function))
In [3]:
# Example lambda keyword
product_func = lambda x, y: x*y
print(product_func(10, 20))
print(product_func(10, 2))
In [4]:
concat = lambda x, y: [x, y]
print(concat([1,2,3], 4))
In [6]:
def square(x):
"""
This returns the square of the requested number `x`
"""
return x**2
print(square(10))
In [7]:
print(square(100))
In [8]:
# Assignation to another variable
mySquare = square
print(mySquare(100))
print(square)
print(mySquare)
print(id(square))
print(id(mySquare))
In [13]:
# attributes present
print("*"*30)
print(dir(square))
print("*"*30)
print(mySquare.__name__)
print("*"*30)
print(square.__code__)
print("*"*30)
print(square.__doc__)
In [12]:
square.d = 10
print(dir(square))
In [3]:
print(square(square(square(2))))
In [12]:
product_func = lambda x, y: x*y
sum_func = lambda F, m: lambda x, y: F(x, y)+m
print(sum_func(product_func, 5)(2, 4))
In [13]:
print(sum_func)
In [16]:
print(sum_func(product_func, 5))
In [15]:
print(sum_func(product_func, 5)(3, 5))
13=2*4+5
F -> product_func
m => 5
x -> 2
y -> 4
2*4+5 = 8+5 = 13
In the above example higher-order function that takes two inputs- A function F(x)
and a multiplier m
.
In Python, Function(s) can also be defined within the scope of another function. If this type of function definition is used the inner function is only in scope inside the outer function, so it is most often useful when the inner function is being returned (moving it to the outer scope) or when it is being passed into another function.
Notice that in the below example, a new instance of the function inner()
is created on each call to outer()
. That is because it is defined during the execution of outer()
. The creation of the second instance has no impact on the first.
In [23]:
def outer(a):
"""
Outer function
"""
y = 0
def inner(x):
"""
inner function
"""
y = x*x*a
return(y)
print(a)
return inner
my_out = outer
In [24]:
my_out(102)
Out[24]:
In [25]:
o = outer(10)
b = outer(20)
print("*"*20)
print(b)
print(o)
print("*"*20)
print(o(10))
print(b(10))
In [21]:
def outer():
"""
Outer function
"""
if 'a' in locals():
a +=10
else:
print("~"),
a = 20
def inner(x):
"""
inner function
"""
return(x*x*a)
print(a)
return inner
# oo = outer
# print(oo.__doc__)
o = outer()
print("*"*20)
print(o)
print(o(10))
print(o.__doc__)
In [22]:
b = outer()
print(b)
print(b(30))
print(b.__doc__)
In [9]:
x = 0
def outer():
x = 1
def inner():
x = 2
print("inner:", x)
inner()
print("outer:", x)
outer()
print("global:", x)
lets take the above example, we have two functions, outer
& inner
. We also have x
variable which is present as global
and also present in both the functions.
If we want to access x
of outer
function from inner
function than global
keyword not help. Fortunately, Python provides a keyword nonlocal
which allows inner
functions to access variables to outer
functions as shown in below example.
In [14]:
x = 0
def outer():
x = 1
def inner():
nonlocal x
x = 2
print("inner:",x, "id:", id(x))
inner()
print("outer:",x, "id:", id(x))
outer()
print("global:",x, "id:", id(x))
In [6]:
def outer(a):
"""
Outer function
"""
y = 1
def inner(x):
"""
inner function
"""
nonlocal y
print(y)
y = x*x*a
return("y =" + str(y))
print(a)
return inner
o = outer(10)
b = outer(20)
print("*"*20)
print(o)
print(o(10))
print("*"*20)
print(b)
print(b(10))
In [3]:
# Encapsulation
def increment(current):
def inner_increment(x): # hidden from outer code
return x + 1
next_number = inner_increment(current)
return [current, next_number]
print(increment(10))
NOTE: We can not access directly the inner function as shown below
In [4]:
try:
increment.inner_increment(109)
except Exception as e:
print(e)
In [11]:
### NOT WORKING
def update(str_val):
def updating(ori_str, key, value):
token = "$"
if key in ori_str:
ori_str = ori_str.replace(token+key, value)
return ori_str
keyval = [{"test1": "val_test", "t1" : "val_1"}, {"test2": "val_test2", "t2" : "val_2"}]
keyval1 = [{"test1": "val_test", "t1" : "val_1"}, {"test2": "val_test2", "t2" : "val_2"}]
ori_str = "This is a $test1 and $test2, $t1 and $t2"
# for k in keyval:
# for key, value in k.items():
# ori_str = updateing(ori_str, key, value)
sdd = [ key, value [for key, value in k] for(k in keyval) ]
print(ori_str)
update("D")
In [ ]:
In [18]:
ld = [{'a': 10, 'b': 20}, {'p': 10, 'u': 100}]
[kv for d in ld for kv in d.items()]
Out[18]:
In [10]:
ori_str = "This is a $test;1 and $test2, $t1 and $t2"
print(ori_str.replace("test1", "TEST1"))
print(ori_str)
In [51]:
# Keepin’ it DRY
def process(file_name):
def do_stuff(file_process):
for line in file_process:
print(line)
if isinstance(file_name, str):
with open(file_name, 'r') as f:
do_stuff(f)
else:
do_stuff(file_name)
process(["test", "test3", "t33"])do_stuff(file_name)
process("test.txt")
or have similar logic which can be replaced by a function, such as mathematical functions, or code base which can be clubed by using some parameters.
In [52]:
def square(n):
return n**2
def cube(n):
return n**3
print(square(2))
In [37]:
def sqr(a, b):
return a**b
??? why code
In [24]:
def test():
print("TESTTESTTEST")
def yes(name):
print("Ja, ", name)
return True
return yes
d = test()
print("XSSSS")
print(d("Venky"))
In [38]:
def power(exp):
def subfunc(a):
return a**exp
return subfunc
square = power(2)
hexa = power(6)
print(square)
print(hexa)
print(square(5)) # 5**2
print()
print(hexa(3)) # 3**6
print(power(6)(3))
# subfunc(3) where exp = 6
# SQuare
# exp -> 2
# Square(5)
# a -> 5
# 5**2
# 25
In [ ]:
Power(6)(3, x)
In [48]:
def a1(m):
x = m * 2
def b(v, t=None):
if t:
print(x, m, t)
return v + t
else:
print(x, m, v)
return v + x
return b
n = a1(2)
print(n(3))
print(n(3, 10))
In [52]:
def f1(a):
def f2(b):
return f2
def f3(c):
return f3
def f4(d):
return f4
def f5(e):
return f5
print (f1(1)(2)(3)(4)(5))
In [55]:
def f1(a):
def f2(b):
def f3(c):
def f4(d):
def f5(e):
print(e)
return f5
return f4
return f3
return f2
f1(1)(2)(3)(4)(5)
They are techniques for implementing lexically scoped name binding with first-class functions. It is a record, storing a function together with an environment. a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created.
A closure—unlike a plain function—allows the function to access those captured variables through the closure's copies of their values or references, even when the function is invoked outside their scope.
In [12]:
def f(x):
def g(y):
return x + y
return g
def h(x):
return lambda y: x + y
a = f(1)
b = h(1)
print(a, b)
print(a(5), b(5))
print(f(1)(5), h(1)(5))
both a and b are closures—or rather, variables with a closure as value—in both cases produced by returning a nested function with a free variable from an enclosing function, so that the free variable binds to the parameter x of the enclosing function. However, in the first case the nested function has a name, g, while in the second case the nested function is anonymous. The closures need not be assigned to a variable, and can be used directly, as in the last lines—the original name (if any) used in defining them is irrelevant. This usage may be deemed an "anonymous closure".
1: Copied from : "https://en.wikipedia.org/wiki/Closure_(computer_programming)"
In [25]:
def make_adder(x):
def add(y):
return x + y
return add
plus10 = make_adder(10)
print(plus10(12)) # make_adder(10).add(12)
print(make_adder(10)(12))
Closures can avoid the use of global values and provides some form of data hiding. It can also provide an object oriented solution to the problem.
When there are few methods (one method in most cases) to be implemented in a class, closures can provide an alternate and more elegant solutions. But when the number of attributes and methods get larger, better implement a class.
In functional programming, functions can be treated as objects. That is, they can assigned to a variable, can be passed as arguments or even returned from other functions.
In [2]:
a = 10
def test_function():
pass
print(id(a), dir(a))
print(id(test_function), dir(test_function))
In [3]:
# Example lambda keyword
product_func = lambda x, y: x*y
print(product_func(10, 20))
print(product_func(10, 2))
In [4]:
concat = lambda x, y: [x, y]
print(concat([1,2,3], 4))
In [6]:
def square(x):
"""
This returns the square of the requested number `x`
"""
return x**2
print(square(10))
In [7]:
print(square(100))
In [8]:
# Assignation to another variable
mySquare = square
print(mySquare(100))
print(square)
print(mySquare)
print(id(square))
print(id(mySquare))
In [13]:
# attributes present
print("*"*30)
print(dir(square))
print("*"*30)
print(mySquare.__name__)
print("*"*30)
print(square.__code__)
print("*"*30)
print(square.__doc__)
In [12]:
square.d = 10
print(dir(square))
In [3]:
print(square(square(square(2))))
In [12]:
product_func = lambda x, y: x*y
sum_func = lambda F, m: lambda x, y: F(x, y)+m
print(sum_func(product_func, 5)(2, 4))
In [13]:
print(sum_func)
In [16]:
print(sum_func(product_func, 5))
In [15]:
print(sum_func(product_func, 5)(3, 5))
13=2*4+5
F -> product_func
m => 5
x -> 2
y -> 4
2*4+5 = 8+5 = 13
In the above example higher-order function that takes two inputs- A function F(x)
and a multiplier m
.
In Python, Function(s) can also be defined within the scope of another function. If this type of function definition is used the inner function is only in scope inside the outer function, so it is most often useful when the inner function is being returned (moving it to the outer scope) or when it is being passed into another function.
Notice that in the below example, a new instance of the function inner()
is created on each call to outer()
. That is because it is defined during the execution of outer()
. The creation of the second instance has no impact on the first.
In [23]:
def outer(a):
"""
Outer function
"""
y = 0
def inner(x):
"""
inner function
"""
y = x*x*a
return(y)
print(a)
return inner
my_out = outer
In [24]:
my_out(102)
Out[24]:
In [25]:
o = outer(10)
b = outer(20)
print("*"*20)
print(b)
print(o)
print("*"*20)
print(o(10))
print(b(10))
In [21]:
def outer():
"""
Outer function
"""
if 'a' in locals():
a +=10
else:
print("~"),
a = 20
def inner(x):
"""
inner function
"""
return(x*x*a)
print(a)
return inner
# oo = outer
# print(oo.__doc__)
o = outer()
print("*"*20)
print(o)
print(o(10))
print(o.__doc__)
In [22]:
b = outer()
print(b)
print(b(30))
print(b.__doc__)
In [9]:
x = 0
def outer():
x = 1
def inner():
x = 2
print("inner:", x)
inner()
print("outer:", x)
outer()
print("global:", x)
lets take the above example, we have two functions, outer
& inner
. We also have x
variable which is present as global
and also present in both the functions.
If we want to access x
of outer
function from inner
function than global
keyword not help. Fortunately, Python provides a keyword nonlocal
which allows inner
functions to access variables to outer
functions as shown in below example.
In [14]:
x = 0
def outer():
x = 1
def inner():
nonlocal x
x = 2
print("inner:",x, "id:", id(x))
inner()
print("outer:",x, "id:", id(x))
outer()
print("global:",x, "id:", id(x))
In [6]:
def outer(a):
"""
Outer function
"""
y = 1
def inner(x):
"""
inner function
"""
nonlocal y
print(y)
y = x*x*a
return("y =" + str(y))
print(a)
return inner
o = outer(10)
b = outer(20)
print("*"*20)
print(o)
print(o(10))
print("*"*20)
print(b)
print(b(10))
In [3]:
# Encapsulation
def increment(current):
def inner_increment(x): # hidden from outer code
return x + 1
next_number = inner_increment(current)
return [current, next_number]
print(increment(10))
NOTE: We can not access directly the inner function as shown below
In [4]:
try:
increment.inner_increment(109)
except Exception as e:
print(e)
In [11]:
### NOT WORKING
def update(str_val):
def updating(ori_str, key, value):
token = "$"
if key in ori_str:
ori_str = ori_str.replace(token+key, value)
return ori_str
keyval = [{"test1": "val_test", "t1" : "val_1"}, {"test2": "val_test2", "t2" : "val_2"}]
keyval1 = [{"test1": "val_test", "t1" : "val_1"}, {"test2": "val_test2", "t2" : "val_2"}]
ori_str = "This is a $test1 and $test2, $t1 and $t2"
# for k in keyval:
# for key, value in k.items():
# ori_str = updateing(ori_str, key, value)
sdd = [ key, value [for key, value in k] for(k in keyval) ]
print(ori_str)
update("D")
In [ ]:
In [18]:
ld = [{'a': 10, 'b': 20}, {'p': 10, 'u': 100}]
[kv for d in ld for kv in d.items()]
Out[18]:
In [10]:
ori_str = "This is a $test;1 and $test2, $t1 and $t2"
print(ori_str.replace("test1", "TEST1"))
print(ori_str)
In [52]:
# Keepin’ it DRY
def process(file_name):
def do_stuff(file_process):
for line in file_process:
print(line)
if isinstance(file_name, str):
with open(file_name, 'r') as f:
do_stuff(f)
else:
do_stuff(file_name)
process(["test", "test3", "t33"])
# process("test.txt")
or have similar logic which can be replaced by a function, such as mathematical functions, or code base which can be clubed by using some parameters.
In [52]:
def square(n):
return n**2
def cube(n):
return n**3
print(square(2))
In [37]:
def sqr(a, b):
return a**b
??? why code
In [24]:
def test():
print("TESTTESTTEST")
def yes(name):
print("Ja, ", name)
return True
return yes
d = test()
print("XSSSS")
print(d("Venky"))
In [38]:
def power(exp):
def subfunc(a):
return a**exp
return subfunc
square = power(2)
hexa = power(6)
print(square)
print(hexa)
print(square(5)) # 5**2
print()
print(hexa(3)) # 3**6
print(power(6)(3))
# subfunc(3) where exp = 6
# SQuare
# exp -> 2
# Square(5)
# a -> 5
# 5**2
# 25
In [ ]:
Power(6)(3, x)
In [48]:
def a1(m):
x = m * 2
def b(v, t=None):
if t:
print(x, m, t)
return v + t
else:
print(x, m, v)
return v + x
return b
n = a1(2)
print(n(3))
print(n(3, 10))
In [52]:
def f1(a):
def f2(b):
return f2
def f3(c):
return f3
def f4(d):
return f4
def f5(e):
return f5
print (f1(1)(2)(3)(4)(5))
In [55]:
def f1(a):
def f2(b):
def f3(c):
def f4(d):
def f5(e):
print(e)
return f5
return f4
return f3
return f2
f1(1)(2)(3)(4)(5)
They are techniques for implementing lexically scoped name binding with first-class functions. It is a record, storing a function together with an environment. a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created.
A closure—unlike a plain function—allows the function to access those captured variables through the closure's copies of their values or references, even when the function is invoked outside their scope.
In [12]:
def f(x):
def g(y):
return x + y
return g
def h(x):
return lambda y: x + y
a = f(1)
b = h(1)
print(a, b)
print(a(5), b(5))
print(f(1)(5), h(1)(5))
both a and b are closures—or rather, variables with a closure as value—in both cases produced by returning a nested function with a free variable from an enclosing function, so that the free variable binds to the parameter x of the enclosing function. However, in the first case the nested function has a name, g, while in the second case the nested function is anonymous. The closures need not be assigned to a variable, and can be used directly, as in the last lines—the original name (if any) used in defining them is irrelevant. This usage may be deemed an "anonymous closure".
1: Copied from : "https://en.wikipedia.org/wiki/Closure_(computer_programming)"
In [25]:
def make_adder(x):
def add(y):
return x + y
return add
plus10 = make_adder(10)
print(plus10(12)) # make_adder(10).add(12)
print(make_adder(10)(12))
Closures can avoid the use of global values and provides some form of data hiding. It can also provide an object oriented solution to the problem.
When there are few methods (one method in most cases) to be implemented in a class, closures can provide an alternate and more elegant solutions. But when the number of attributes and methods get larger, better implement a class.
In [ ]: